package org.teamswift.crow.rbac.controller;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.teamswift.crow.rbac.annotation.LoginRequiredOnly;
import org.teamswift.crow.rbac.annotation.PublicAction;
import org.teamswift.crow.rbac.cmd.CrowRbac;
import org.teamswift.crow.rbac.dto.ChangePasswordDto;
import org.teamswift.crow.rbac.dto.CrowLoginCredentialDto;
import org.teamswift.crow.rbac.entity.CrowUser;
import org.teamswift.crow.rbac.enums.CrowLoginType;
import org.teamswift.crow.rbac.enums.CrowRbacErrors;
import org.teamswift.crow.rbac.exceptions.CredentialInvalidException;
import org.teamswift.crow.rbac.security.CrowSecurityUtils;
import org.teamswift.crow.rbac.security.PasswordHelper;
import org.teamswift.crow.rbac.security.token.CrowUsernameToken;
import org.teamswift.crow.rbac.security.token.CrowVerifyCodeToken;
import org.teamswift.crow.rbac.service.CrowUserService;
import org.teamswift.crow.rbac.vo.CrowUserVo;
import org.teamswift.crow.rest.exception.BusinessException;
import org.teamswift.crow.rest.result.CrowResult;
import org.teamswift.crow.rest.result.CrowResultCode;
import org.teamswift.crow.rest.result.ICrowResult;
import org.teamswift.crow.rest.utils.CrowMessageUtil;
import org.teamswift.crow.rest.utils.DozerUtils;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/crow/rbac/session")
@PublicAction
@Transactional(rollbackFor = Exception.class)
public class CrowSessionController {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    private final CrowSecurityUtils crowSecurityUtils;

    private final CrowUserService crowUserService;

    public CrowSessionController(CrowSecurityUtils crowSecurityUtils, CrowUserService crowUserService) {
        this.crowSecurityUtils = crowSecurityUtils;
        this.crowUserService = crowUserService;
    }

    @RequestMapping(value = "/me")
    @LoginRequiredOnly
    public ICrowResult<CrowUserVo> me() {
        CrowUser user = (CrowUser) SecurityUtils.getSubject().getPrincipal();

        user = crowUserService.getOne(user);

        CrowUserVo userVo = DozerUtils.map(user, CrowUserVo.class);
        return CrowResult.ofSuccess(userVo);
    }

    @RequestMapping(value = "/changePassword", method = RequestMethod.PUT)
    @LoginRequiredOnly
    public void changePassword(@RequestBody ChangePasswordDto dto) {
        CrowUser user = (CrowUser) SecurityUtils.getSubject().getPrincipal();

        if(!dto.getNewPasswordRepeat().equals(dto.getNewPassword())) {
            throw new BusinessException(
                    CrowMessageUtil.error(CrowRbacErrors.TwoPasswordNotEqual)
            );
        }

        if(!PasswordHelper.encrypt(dto.getOldPassword(), user.getSalt()).equals(user.getPassword())) {
            throw new BusinessException(
                    CrowMessageUtil.error(CrowRbacErrors.CredentialInvalid)
            );
        }

        String salt = PasswordHelper.generateSalt();
        user.setSalt(salt);
        user.setPassword(PasswordHelper.encrypt(dto.getNewPassword(), salt));
    }

    @RequestMapping(method = RequestMethod.POST)
    @LoginRequiredOnly
    public ICrowResult<Map<String, String>> loginViaUsername(@Validated @RequestBody CrowLoginCredentialDto dto) {
        AuthenticationToken token;

        CrowLoginType loginType = dto.getLoginType();
        switch(loginType) {
            case Password:
                token = new CrowUsernameToken(
                        dto.getPrincipal(), dto.getCredential()
                );
                break;
            case VerifyCode:
                token = new CrowVerifyCodeToken(
                        dto.getPrincipal(), dto.getCredential()
                );
                break;
            default:
                throw new BusinessException(
                        CrowResultCode.PARAM_IS_INVALID,
                        CrowMessageUtil.error(
                            CrowRbacErrors.UnSupportedLoginType,
                            loginType
                        )
                );
        }

        Subject subject = SecurityUtils.getSubject();

        try {
            subject.login(token);
        } catch (AuthenticationException e) {
            logger.warn(e.getMessage());
            throw new CredentialInvalidException(
                    CrowMessageUtil.error(CrowRbacErrors.CredentialInvalid)
            );
        }

        CrowUser user = (CrowUser) subject.getPrincipal();

        String jwtToken = crowSecurityUtils.signByUser(user);
        Map<String, String> result = new HashMap<>() {{
            put("token", jwtToken);
        }};
        return CrowResult.ofSuccess(result);
    }

    @RequestMapping(method = RequestMethod.DELETE)
    @LoginRequiredOnly
    public void logout() {

    }

}
